/**
 * Created on May 11, 2007
 */
package jvs.vfs.tools;

import java.awt.Component;
import java.awt.Desktop;
import java.awt.Frame;
import java.awt.Desktop.Action;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.StringTokenizer;

import javax.swing.CellEditor;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import jvs.vfs.FileSystem;
import jvs.vfs.util.Log;
import x.java.io.File;

/**
 * @author qiangli
 * 
 */
public class FileTree extends JTree implements ActionListener,
		CellEditorListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private JPopupMenu popup;

	private TreePath cutPath = null;

	private TreePath copyPath = null;

	private JMenuItem openmi;

	private JMenuItem browsemi;

	private JMenuItem newfilemi;

	private JMenuItem newfoldermi;

	private JMenuItem cutmi;

	private JMenuItem copymi;

	private JMenuItem pastemi;

	private JMenuItem deletemi;

	private JMenuItem renamemi;

	private JMenuItem mountmi;

	private JMenuItem propertiesmi;
	
	private JMenu openwith;
	
	private FileExplorer owner;
	
	public FileTree(FileExplorer owner,TreeModel model) {
		super(model);
		this.owner = owner;
		//
		setEditable(true);
	
		getSelectionModel().setSelectionMode(
				TreeSelectionModel.SINGLE_TREE_SELECTION);
		DefaultTreeCellRenderer renderer = new MyCellRenderer();
		setCellRenderer(renderer);
		setCellEditor(new MyCellEditor(this, renderer));
		//
		popup = createPopup();
		//

		addMouseListener(new MouseAdapter() {
			public void mouseReleased(MouseEvent me) {
				if (me.isPopupTrigger()) {
					verifyPopup();
					popup.show((JComponent) me.getSource(), me.getX(), me.getY());
				}
			}

			public void mousePressed(MouseEvent me) {
				int selRow = getRowForLocation(me.getX(), me.getY());
				TreePath path = getPathForLocation(me.getX(), me.getY());
				if (selRow != -1) {
					if (me.getClickCount() == 1) {
					} else if (me.getClickCount() == 2) {
						File f = (File) path.getLastPathComponent();
						try {
							Shell.launch(f);
						} catch (Exception e) {
							e.printStackTrace();
							showStatus(e.getMessage());
						}
					}
				}
			}
		});
	}

	protected void verifyPopup() {
		//
		openmi.setEnabled(true);
		browsemi.setEnabled(true);
		//
		newfilemi.setEnabled(false);
		newfoldermi.setEnabled(false);
		cutmi.setEnabled(false);
		copymi.setEnabled(false);
		pastemi.setEnabled(false);
		deletemi.setEnabled(false);
		renamemi.setEnabled(false);
		mountmi.setEnabled(false);
		propertiesmi.setEnabled(false);
		//
		boolean isdir = false;
		//boolean isfile = false;
		boolean isroot = false;
		boolean ismp = false;
		//
		TreePath p = getSelectionPath();
		if (p != null) {
			File f = (File) p.getLastPathComponent();
			isdir = f.isDirectory();
			//isfile = !isdir;
			isroot = FileSystem.getFileSystem().isRoot(f);
			ismp = FileSystem.getFileSystem().isMountPoint(f);
		}
		//
		if (p != null) {
			newfilemi.setEnabled(isdir);
			newfoldermi.setEnabled(isdir);
			cutmi.setEnabled(!isroot);
			copymi.setEnabled(true);
			deletemi.setEnabled(!isroot);
			renamemi.setEnabled(!isroot);
			mountmi.setEnabled(isdir||ismp);
			propertiesmi.setEnabled(true);
		}
		if (cutPath != null) {
			pastemi.setEnabled(true);
		}
		if (copyPath != null) {
			pastemi.setEnabled(true);
		}
		//
		mountmi.setText(ismp?"Unmount":"Mount");
		mountmi.setActionCommand(ismp?"unmount":"mount");
	}

	private JMenuItem createMenuItem(JPopupMenu p, String name, String cmd) {
		JMenuItem mi = new JMenuItem(name);
		mi.addActionListener(this);
		mi.setActionCommand(cmd);
		p.add(mi);
		return mi;
	}
	
	private JMenuItem createMenuItem(JMenu p, String name, String cmd) {
		JMenuItem mi = new JMenuItem(name);
		mi.addActionListener(this);
		mi.setActionCommand(cmd);
		p.add(mi);
		return mi;
	}

	protected JPopupMenu createPopup() {
		JPopupMenu p = new JPopupMenu();
		openmi = createMenuItem(p, "Open", "open");
		browsemi = createMenuItem(p,"Browse","browse");
		//
		try {
			File f = new File("/etc/application/fe/contextmenu.conf");
			if (f.exists()) {
				String ctxmenu = f.getContent();
				StringTokenizer st = new StringTokenizer(ctxmenu,"\r\n");
				openwith = new JMenu("Open With");	
				int cnt = 0;
				while(st.hasMoreTokens()) {
					String s = st.nextToken().trim();
					if (s.length() == 0) {
						continue;
					}
					if (s.startsWith("#")) {
						continue;
					}
					int idx = s.indexOf("#");
					if (idx > 0) {
						s = s.substring(0,idx);
					}
					idx = s.indexOf("=");
					if (idx > 0) {
						String exe = s.substring(0,idx).trim();
						String name = s.substring(idx+1).trim();
						createMenuItem(openwith, name, "open."+exe);
						cnt++;
					}
				}
				if (cnt > 0) p.add(openwith);
			}
		} catch (Exception e) {
			Log.log(Log.DEBUG,this,e);
		}
		//
		p.addSeparator();
		//runmi = createMenuItem(p, "Run...", "run");
		//p.addSeparator();
		newfilemi = createMenuItem(p, "New File", "newfile");
		newfoldermi = createMenuItem(p, "New Folder", "newfolder");
		p.addSeparator();
		cutmi = createMenuItem(p, "Cut", "cut");
		copymi = createMenuItem(p, "Copy", "copy");
		pastemi = createMenuItem(p, "Paste", "paste");
		p.addSeparator();
		deletemi = createMenuItem(p, "Delete", "delete");
		renamemi = createMenuItem(p, "Rename", "rename");
		p.addSeparator();
		mountmi = createMenuItem(p, "Mount", "mount");
		p.addSeparator();
		propertiesmi = createMenuItem(p, "Properties", "properties");
		return p;
	}
	
	private static java.io.File nativeFile(File file) throws IOException {
		x.java.io.File f = (x.java.io.File) file;
		URI uri = f.getNativeURI();//f.toRealURI();
		return new java.io.File(URLDecoder.decode(uri.getPath(),"UTF-8"));
	}
	
	/*
	 * Note: fireTreeStructureChanged() needs to be fired for actions: create file/folder, paste, rename 
	 *        due to sorting
	 * 
	 */
	public void actionPerformed(ActionEvent ae) {

		TreePath path = getSelectionPath();

		File f = (File) path.getLastPathComponent();

		if (ae.getActionCommand().equals("open")) {
			try {
				if (Desktop.isDesktopSupported()) {
					Desktop dt = Desktop.getDesktop();
					if (f.isDirectory() && f.isNative() && dt.isSupported(Action.BROWSE)) {
						dt.browse(f.toRealURI());
						return;
					} 
					if (f.isFile() && dt.isSupported(Action.OPEN)) {
						dt.open(nativeFile(f));
						return;
					} 
				}
				Shell.launch(f);
			} catch (Exception e) {
				e.printStackTrace();
				showStatus(f+": "+e.getMessage());
			}
		} else if (ae.getActionCommand().equals("browse")) {
			try {
				if (Desktop.isDesktopSupported()) {
					Desktop dt = Desktop.getDesktop();
					if (dt.isSupported(Action.BROWSE)) {
						String host = x.java.lang.System.getProperty("http.host","localhost");
						int port = Integer.parseInt(x.java.lang.System.getProperty("http.port","80"));
						String p = f.getPath();
						URL url = new URL("http",host,port,p);
						dt.browse(url.toURI());
					} 
				} else {
					Shell.exec("browse",new String[]{f.getPath()});
				}
			} catch (Exception e) {
				showStatus(f+": "+e.getMessage());
			}
		} else if (ae.getActionCommand().startsWith("open.")) {
			String cmd = ae.getActionCommand();
			int idx = cmd.indexOf(".");
			cmd = cmd.substring(idx+1);
			try {
				Shell.exec(cmd, new String[] { f.getPath() });
			} catch (Exception e) {
				showStatus(cmd+": "+e.getMessage());
			}
		} else if (ae.getActionCommand().equals("run")) {
			try {
				Frame frame = JOptionPane.getFrameForComponent(this);
				String cmd = (String)JOptionPane.showInputDialog(
	                    frame,
	                    "Enter command below:\n",
	                    "Run...",
	                    JOptionPane.PLAIN_MESSAGE);
				
					if ((cmd != null) && (cmd.length() > 0)) {
						Shell.exec(cmd, new String[] { f.getPath() });
						return;
					}
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("newfile")) {
			try {
				boolean isdir = f.isDirectory();
				File parent = (File) (isdir ? f : f.getParentFile());
				File child = new File(parent, "New_File");
				if (child.exists()) {
					return;
				}
				path = isdir ? path : path.getParentPath();
				FileSystemModel fsm = (FileSystemModel) getModel();
				int[] childIndices = new int[] { fsm.getChildCount(parent)};
				
				child.createNewFile();
				
				TreeModelEvent tme = new TreeModelEvent(this, path,
						childIndices, new Object[] { child });
				//fsm.fireTreeNodesInserted(tme);
				fsm.fireTreeStructureChanged(tme);
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("newfolder")) {
			try {
				boolean isdir = f.isDirectory();
				File parent = (File) (isdir ? f : f.getParentFile());
				File child = new File(parent, "New_Folder");
				if (child.exists()) {
					return;
				}
				path = isdir ? path : path.getParentPath();
				FileSystemModel fsm = (FileSystemModel) getModel();
				int[] childIndices = new int[] { 0 };
				
				child.mkdir();
				
				TreeModelEvent tme = new TreeModelEvent(this, path,
						childIndices, new Object[] { child });
				//fsm.fireTreeNodesInserted(tme);
				fsm.fireTreeStructureChanged(tme);
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("cut")) {
			cutPath = path;
			copyPath = null;
		} else if (ae.getActionCommand().equals("copy")) {
			copyPath = path;
			cutPath = null;
		} else if (ae.getActionCommand().equals("paste")) {
			try {
				if (copyPath == null && cutPath == null) {
					return;//
				}
				TreePath srcPath = (copyPath == null ? cutPath : copyPath);
				File srcFile = (File) srcPath.getLastPathComponent();
				boolean isdir = f.isDirectory();
				File dest = isdir ? f : (File) f.getParentFile();
				path = isdir ? path : path.getParentPath();

				File destFile = new File(dest, srcFile.getName());
				if (destFile.exists()) {
					showStatus("File exists: " + destFile);
					return;
				}
				FileSystemModel fsm = (FileSystemModel) getModel();
				int idx = fsm.getChildCount(dest) - 1;
				if (idx < 0) {
					idx = 0;
				}
				TreeModelEvent tme = new TreeModelEvent(this, path,
						new int[] { idx }, new Object[] { destFile });
				
				idx = fsm.getIndexOfChild(srcFile.getParentFile(), srcFile);
				if (copyPath != null) {
					srcFile.copy(destFile);
				} else if (cutPath != null) {
					srcFile.renameTo(destFile);
				}
				//fsm.fireTreeNodesInserted(tme);
				fsm.fireTreeStructureChanged(tme);
				//
				if (cutPath != null) {
					tme = new TreeModelEvent(this, cutPath.getParentPath(),
							new int[] { idx }, new Object[] { srcFile });
					fsm.fireTreeNodesRemoved(tme);
				}
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("delete")) {
			try {
				File parent = (File) f.getParentFile();
				path = path.getParentPath();
				FileSystemModel fsm = (FileSystemModel) getModel();
				int[] childIndices = new int[] { fsm.getIndexOfChild(parent, f) };
				f.delete();
				TreeModelEvent tme = new TreeModelEvent(this, path,
						childIndices, new Object[] { f });
				fsm.fireTreeNodesRemoved(tme);
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("rename")) {
			getCellEditor().addCellEditorListener(this);
			startEditingAtPath(path);
		} else if (ae.getActionCommand().equals("mount")) {
			try {
				Frame frame = JOptionPane.getFrameForComponent(this);
				MountDialog dialog = new MountDialog(frame,f.getPath());
				dialog.setLocationRelativeTo(owner);
				int op = dialog.showDialog();
				if (op == MountDialog.APPROVE_OPTION) {
					FileSystem fs = FileSystem.getFileSystem();
					fs.mount(f.getPath(), dialog.getURI(), dialog.getOptions());
					//
					File parent = (File) f.getParentFile();
					path = path.getParentPath();
					FileSystemModel fsm = (FileSystemModel) getModel();
					//
					int[] childIndices = new int[] { fsm.getIndexOfChild(parent, f) };
					TreeModelEvent tme = new TreeModelEvent(this, path,
							childIndices, new Object[] { f });
					fsm.fireTreeStructureChanged(tme);
					//
				}
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("unmount")) {
			try {
				Frame frame = JOptionPane.getFrameForComponent(this);
				int op = MessageDialog.confirm(frame, "Are you sure?","Unmount "+f.getPath());
				if (op == MessageDialog.APPROVE_OPTION) {
					FileSystem fs = FileSystem.getFileSystem();
					if (fs.isMountPoint(f)) {
						fs.unmount(f.getPath());
						//
						File parent = (File) f.getParentFile();
						path = path.getParentPath();

						FileSystemModel fsm = (FileSystemModel) getModel();
						int[] childIndices = new int[] { fsm.getIndexOfChild(parent, f) };
						TreeModelEvent tme = new TreeModelEvent(this, path,
								childIndices, new Object[] { f });
						fsm.fireTreeStructureChanged(tme);
					}
				}
			} catch (Exception e) {
				showStatus(e.getMessage());
			}
		} else if (ae.getActionCommand().equals("properties")) {
			Frame frame = JOptionPane.getFrameForComponent(this);
			PropertiesDialog dialog = new PropertiesDialog(frame,f);
			dialog.showDialog();
		}
	}


	class MyCellRenderer extends DefaultTreeCellRenderer {

		private static final long serialVersionUID = 1L;

		public Component getTreeCellRendererComponent(JTree tree, Object value,
				boolean sel, boolean expanded, boolean leaf, int row,
				boolean hasFocus) {
			return super.getTreeCellRendererComponent(tree, value, sel,
					expanded, leaf, row, hasFocus);
		}

	}

	class MyCellEditor extends DefaultTreeCellEditor {

		public MyCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
			super(tree, renderer);

		}

		public Component getTreeCellEditorComponent(JTree tree, Object value,
				boolean isSelected, boolean expanded, boolean leaf, int row) {

			return super.getTreeCellEditorComponent(tree, value, isSelected,
					expanded, leaf, row);
		}

		public void valueChanged(TreeSelectionEvent e) {
			super.valueChanged(e);
		}

	}

	private void showStatus(String s) {
		owner.statusbar.show(s);
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.event.CellEditorListener#editingCanceled(javax.swing.event.ChangeEvent)
	 */
	public void editingCanceled(ChangeEvent ce) {
		CellEditor editor = (CellEditor) ce.getSource();
		editor.removeCellEditorListener(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.event.CellEditorListener#editingStopped(javax.swing.event.ChangeEvent)
	 */
	public void editingStopped(ChangeEvent ce) {
		CellEditor editor = (CellEditor) ce.getSource();
		editor.removeCellEditorListener(this);

		String name = editor.getCellEditorValue().toString();
		TreePath path = getEditingPath();
		File f = (File) path.getLastPathComponent();
		try {
			if (name.equals(f.getName())) {
				return;
			}
			FileSystemModel fsm = (FileSystemModel) getModel();
			File parent = (File) f.getParentFile();
			File child = new File(parent, name);
			int[] childIndices = new int[] { fsm.getIndexOfChild(parent, f) };
	
			f.renameTo(child);
			
			TreeModelEvent tme = new TreeModelEvent(this, path.getParentPath(),
					childIndices, new Object[] { child });
		
			//fsm.fireTreeNodesChanged(tme);
			fsm.fireTreeStructureChanged(tme);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}
